using UnityEngine;
using System.Collections;

public class ShipMoverAxisDependant : MonoBehaviour
{
    //---------------------------------------------------------------------------------------------------------
    //---------------------------------------------------------------------------------------------------------
	#region Variables
	    #region  Input
	        public  AnimationCurve  m_cInputTreatment;          // Curve that transforms the input
			public	bool			m_beNormalizedSpeed = true;			
	    #endregion
	
	    #region  Acceleration
	        public  float           m_fAcceleration = 4;        // The acceleration that multiplies the input
	        public  float           m_fBrake 		= 4;        // The acceleration that multiplies the input when brakeing
	    #endregion
	
	    #region  Speed
	        public  float         	m_fSpeedHardMaxFront;       // The max speed
	        public  float         	m_fSpeedHardMaxBack;        // The max speed
	        public  float         	m_fSpeedHardMaxVertical;    // The max speed
	        public  AnimationCurve  m_cSpeedMax;                // Curve that relates the input to the soft max speed
	        public	float           m_vSpeedSoftMaxFront;       // The current max speed related to the input
	        public	float           m_vSpeedSoftMaxBack;        // The current max speed related to the input
	        public	float           m_vSpeedSoftMaxVertical;    // The current max speed related to the input
	        public  float         	m_fSpeedMaxSofteningSpeed;  // Speed at whitch the sof max speed is reduced (increment is instantaneous)
	        public	Vector2         m_vSpeedCurrent;            // The current speed
	        public	Vector2         m_vSpeedCurrentCosSin;      // The cosin and sin of the current speed
			public	float			m_fSpeedMagnitude;  
	    #endregion
	
	    #region  Friction
	        public  float           m_fFrictionMaxVertical;     // The maximum ammount of friction
	        public  float           m_fFrictionMaxFront;        // The maximum ammount of friction
	        public  float           m_fFrictionMaxBack;         // The maximum ammount of friction
	        public  float         	m_fFrictionMaxSpeedFront;   // The maximum speed considered by the friction
	        public  float         	m_fFrictionMaxSpeedBack;    // The maximum speed considered by the friction
	        public  float         	m_fFrictionMaxSpeedVertical;// The maximum speed considered by the friction
	        public  AnimationCurve  m_cFrictionFront;           // Curve that relates the friction with the speed 
	        public  AnimationCurve  m_cFrictionBack;            // Curve that relates the friction with the speed 
	        public  AnimationCurve  m_cFrictionVertical;        // Curve that relates the friction with the speed 
	    #endregion
	
	    #region  External forces
	        public  Vector2         m_vResistance;              // A force that opposes the velocity directionally
	        public  Vector2         m_vExternalAcceleration;    // A force aplied as an extra acceleration
	        public 	Vector2         m_vExternalConstVelocity;   // A constant velocity applyed externaly and independently
	        public 	float	        m_fExternalConstFriction;   // A constant force that oposes all movement
	    #endregion
	
	#endregion
	
    //---------------------------------------------------------------------------------------------------------
    // Use this for initialization
	// It is called before the first "Update" (ED)
    //---------------------------------------------------------------------------------------------------------
    void Start()
    {
        m_vSpeedCurrent =  Vector2.zero;
	}


    //---------------------------------------------------------------------------------------------------------
    //---------------------------------------------------------------------------------------------------------
	public Vector2 GetNewDisplacement()
	{
		// Get and treat the input (ED)
		//---------------------------------------------------------------------------------------------------------
        Vector2 vPureInput		= new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));
		Vector2 vTreatedInput	= ComputeInputMovement(ref vPureInput);
		
		
		// Get the new speed
        //---------------------------------------------------------------------------------------------------------
		ComputeSpeed(vPureInput, vTreatedInput);
       	
		m_fSpeedMagnitude = m_vSpeedCurrent.magnitude;
		
		// Apply the external velocity (ED)
        //---------------------------------------------------------------------------------------------------------
        return	(m_vSpeedCurrent + m_vExternalConstVelocity) * Time.deltaTime;
	}
	
    //---------------------------------------------------------------------------------------------------------
    //---------------------------------------------------------------------------------------------------------
	private Vector2 ComputeInputMovement(ref Vector2 _vPureImput)
	{
        // Normalize it to have the same result with keyboard, D_Pad and analog(normalized by construction) (ED)
        //---------------------------------------------------------------------------------------------------------
		float fInputMagnitude = _vPureImput.sqrMagnitude;
		if(fInputMagnitude > 1)
		{
			_vPureImput.Normalize();
		}
        

        // Modify it with a curve(ED)
        //---------------------------------------------------------------------------------------------------------
        _vPureImput.x = Mathf.Sign(_vPureImput.x) * m_cInputTreatment.Evaluate(Mathf.Abs(_vPureImput.x));
        _vPureImput.y = Mathf.Sign(_vPureImput.y) * m_cInputTreatment.Evaluate(Mathf.Abs(_vPureImput.y));
		
		return _vPureImput;
	}
	
    //---------------------------------------------------------------------------------------------------------
    //---------------------------------------------------------------------------------------------------------
	private void ComputeCurSpeedCosAndSen()
	{
		float fAngle 			= Vector2.Angle(m_vSpeedCurrent, Vector2.right);
		m_vSpeedCurrentCosSin.x	= Mathf.Abs(Mathf.Cos(fAngle * Mathf.Deg2Rad));
		m_vSpeedCurrentCosSin.y	= Mathf.Abs(Mathf.Sin(fAngle * Mathf.Deg2Rad));
	}
	
    //---------------------------------------------------------------------------------------------------------
    //---------------------------------------------------------------------------------------------------------
	private void ComputeSpeed(Vector2 _vPureInput, Vector2 _vTreatedInput)
	{
		// Update the speed through acceleration (ED)
        //---------------------------------------------------------------------------------------------------------
		ApplyAcceleration(ref _vTreatedInput);       
        		

        // Apply friction (ED)
        //---------------------------------------------------------------------------------------------------------
		ComputeCurSpeedCosAndSen();
        
		ApplyFriction(ref _vTreatedInput);
		
		ApplyExternalFriction();
		
		
        // Update the soft max speed (ED)
        //---------------------------------------------------------------------------------------------------------
        ComputeSoftMaxSpeed(ref _vTreatedInput);


        // Limit the speed (ED)
        //---------------------------------------------------------------------------------------------------------
		ComputeCurSpeedCosAndSen();
       	ApplySpeedLimit(ref _vPureInput);


        // Apply the external acceleration (ED)
        //---------------------------------------------------------------------------------------------------------
       	ApplyExternalAcceleration();


        // Apply the resistance only if going against it (ED)
        //---------------------------------------------------------------------------------------------------------
		ApplyForceResistance();
	}
	
    //---------------------------------------------------------------------------------------------------------
    //---------------------------------------------------------------------------------------------------------
	private void ApplyAcceleration(ref Vector2 _vTreatedInput)
	{
		// Study the oposition of movement
        //---------------------------------------------------------------------------------------------------------
		float fAngle	= Vector2.Angle(_vTreatedInput, m_vSpeedCurrent);
		float fCosCoef	= (Mathf.Cos(fAngle) + 1) * 0.5f;
		
		// Get the acceleration (ED)
        //---------------------------------------------------------------------------------------------------------
		Vector2 vAcceleration = _vTreatedInput * Mathf.Abs(m_fAcceleration * fCosCoef + m_fBrake * (1 - fCosCoef));
		/*
		 * if(_vTreatedInput.x * m_vSpeedCurrent.x >= 0)
		{
			vAcceleration.x = _vTreatedInput.x * m_fAcceleration;
		}
		else
		{
			vAcceleration.x = _vTreatedInput.x * m_fBrake;
		}
		if(_vTreatedInput.y * m_vSpeedCurrent.y >= 0)
		{
			vAcceleration.y = _vTreatedInput.y * m_fAcceleration;
		}
		else
		{
			vAcceleration.y = _vTreatedInput.y * m_fBrake;
		}
		*/
		
		// Update the speed (ED)
        //---------------------------------------------------------------------------------------------------------
        m_vSpeedCurrent         += vAcceleration * Time.deltaTime;
	}
		
    //---------------------------------------------------------------------------------------------------------
	//Apply the friction when brakeing or not accelerating (ED)
    //---------------------------------------------------------------------------------------------------------
	private void ApplyFriction(ref Vector2 _vTreatedInput)
	{
		if(_vTreatedInput.x * m_vSpeedCurrent.x <= 0) 
		{
			if (m_vSpeedCurrent.x > 0)
	        {
	            m_vSpeedCurrent.x = Mathf.Max(0.0f, m_vSpeedCurrent.x - ( m_fFrictionMaxFront 
	                                              * Time.deltaTime
	                                              * m_cFrictionFront.Evaluate( Mathf.Abs(m_vSpeedCurrent.x)
	                                                                         / m_fFrictionMaxSpeedFront
				                                                             * m_vSpeedCurrentCosSin.x)));
	        }
	        else if (m_vSpeedCurrent.x < 0)
	        {
	            m_vSpeedCurrent.x = Mathf.Min(0.0f, m_vSpeedCurrent.x + ( m_fFrictionMaxBack
	                                              * Time.deltaTime
	                                              * m_cFrictionBack.Evaluate( Mathf.Abs(m_vSpeedCurrent.x)
	                                                                        / m_fFrictionMaxSpeedBack
				                                                             * m_vSpeedCurrentCosSin.x)));
	        }
		}
		if(_vTreatedInput.y * m_vSpeedCurrent.y <= 0)
		{
	        if (m_vSpeedCurrent.y != 0)
	        {
	            m_vSpeedCurrent.y = Maths.FloatLinearReduction( m_vSpeedCurrent.y , m_fFrictionMaxVertical
	                                                          * Time.deltaTime
	                                                          * m_cFrictionVertical.Evaluate( Mathf.Abs(m_vSpeedCurrent.y)
	                                                                                        / m_fFrictionMaxSpeedVertical
				                                                             				* m_vSpeedCurrentCosSin.y));
			}
        }
	}
	
    //---------------------------------------------------------------------------------------------------------
    //---------------------------------------------------------------------------------------------------------
	private void ApplyExternalFriction()
	{
		m_vSpeedCurrent = Maths.Vec2LinearReduction(ref m_vSpeedCurrent, m_fExternalConstFriction * Time.deltaTime);
	}
	
    //---------------------------------------------------------------------------------------------------------
    //---------------------------------------------------------------------------------------------------------
	private void ComputeSoftMaxSpeed(ref Vector2 _vTreatedInput)
	{
		float fNewMaxSpeedFront;
        float fNewMaxSpeedBack;
        float fNewMaxSpeedVertical;
        fNewMaxSpeedFront       = m_cSpeedMax.Evaluate(Mathf.Abs(_vTreatedInput.x)) * m_fSpeedHardMaxFront;
        fNewMaxSpeedBack        = m_cSpeedMax.Evaluate(Mathf.Abs(_vTreatedInput.x)) * m_fSpeedHardMaxBack;
        fNewMaxSpeedVertical    = m_cSpeedMax.Evaluate(Mathf.Abs(_vTreatedInput.y)) * m_fSpeedHardMaxVertical;
        if(fNewMaxSpeedFront 	< m_vSpeedSoftMaxFront)
        {
            m_vSpeedSoftMaxFront = Maths.FloatLinearBlend( m_vSpeedSoftMaxFront, fNewMaxSpeedFront
                                                      	 , m_fSpeedMaxSofteningSpeed * Time.deltaTime);
        }
        else
        {
            m_vSpeedSoftMaxFront = fNewMaxSpeedFront; 
        }
		if(fNewMaxSpeedBack 	< m_vSpeedSoftMaxBack)
        {
            m_vSpeedSoftMaxBack = Maths.FloatLinearBlend( m_vSpeedSoftMaxBack, fNewMaxSpeedFront
                                                      	, m_fSpeedMaxSofteningSpeed * Time.deltaTime);
        }
        else
        {
            m_vSpeedSoftMaxBack = fNewMaxSpeedBack; 
        }
        if(fNewMaxSpeedVertical < m_vSpeedSoftMaxVertical)
        {
            m_vSpeedSoftMaxVertical = Maths.FloatLinearBlend( m_vSpeedSoftMaxVertical, fNewMaxSpeedVertical
                                                      		, m_fSpeedMaxSofteningSpeed * Time.deltaTime);
        }
        else
        {
            m_vSpeedSoftMaxVertical = fNewMaxSpeedVertical; 
        }
	}	
	
    //---------------------------------------------------------------------------------------------------------
    //---------------------------------------------------------------------------------------------------------
	private void ApplySpeedLimit(ref Vector2 _vPureInput)
	{
		m_vSpeedCurrent.x 		= Mathf.Clamp(m_vSpeedCurrent.x	, -m_vSpeedSoftMaxBack * m_vSpeedCurrentCosSin.x
		                                  						, m_vSpeedSoftMaxFront * m_vSpeedCurrentCosSin.x);
        m_vSpeedCurrent.y 		= Mathf.Clamp(m_vSpeedCurrent.y	, -m_vSpeedSoftMaxVertical * m_vSpeedCurrentCosSin.y
		                                  						, m_vSpeedSoftMaxVertical * m_vSpeedCurrentCosSin.y);
	}
	
    //---------------------------------------------------------------------------------------------------------
    //---------------------------------------------------------------------------------------------------------
	private void ApplyExternalAcceleration()
	{
        m_vSpeedCurrent += m_vExternalAcceleration * Time.deltaTime;
	}	
	
    //---------------------------------------------------------------------------------------------------------
    //---------------------------------------------------------------------------------------------------------
	private void ApplyForceResistance()
	{
		if(m_vSpeedCurrent != Vector2.zero)
		{
	        float fResistanceCoef = Vector2.Dot(m_vResistance, m_vSpeedCurrent) / m_vSpeedCurrent.magnitude;
	        if (fResistanceCoef < 0)
	        {
	            m_vSpeedCurrent = Maths.Vec2LinearReduction(ref m_vSpeedCurrent, fResistanceCoef * Time.deltaTime);
	        }
		}
	}
}
